Skip to content

feat(s3): add CORS, web console, and SigV4 verification to the standalone image - #19

Merged
simota merged 7 commits into
mainfrom
feat/s3-cors-console-sigv4-20261106
Nov 6, 2026
Merged

simota merged 7 commits into
mainfrom
feat/s3-cors-console-sigv4-20261106

Conversation

@simota

@simota simota commented Nov 6, 2026

Copy link
Copy Markdown
Owner

Summary

Adds CORS, a web console, and complete SigV4 verification to the standalone S3 image (services/s3/Dockerfile). Usage and settings are in services/s3/README.md.

  • CORS: PutBucketCors / GetBucketCors / DeleteBucketCors, preflight, and response headers. DEVCLOUD_S3_CORS_ALLOW_ORIGINS sets a default for buckets without their own rules. Nothing is allowed unless configured.
  • Web console: an embedded React app and JSON API on DEVCLOUD_S3_CONSOLE_ADDR (9001 in the image). Mutations go through the S3 provider protocol. Strict mode adds HTTP Basic, and a Host allowlist stops DNS rebinding.
  • SigV4 in strict mode: verifies aws-chunked chunk and trailer signatures, enforces a 15-minute clock skew and the credential scope date, and requires host and behavioural x-amz-* headers to be signed. The binary refuses an unknown auth mode, or strict mode without credentials.
  • End-to-end suite: npm run test:e2e in web/s3-console builds the image and tests it through real sockets, Chromium, and the AWS SDK for JavaScript.

Bugs fixed

  • Keys with spaces, non-ASCII, or reserved characters failed strict auth because the canonical URI was double-encoded. Presigned URLs for those keys failed the same way.
  • aws-chunked framing was stored as part of the object in relaxed mode, corrupting uploads that use trailing checksums.
  • A non-ASCII x-amz-date panicked while holding the store lock.
  • Transfer-Encoding: chunked bodies were read as empty, and repeated Content-Length values could overwrite an object with the wrong bytes. Conflicting values are now refused.

Behaviour changes to review

  • route_with_cors answers OPTIONS before signature checks, because browsers never sign a preflight. OPTIONS used to return 405 on buckets and objects.
  • Strict mode now rejects requests outside the clock skew window, so services/s3/tests/http_parity.rs signs with the current time instead of a fixed date.
  • The default auth mode stays relaxed, which leaves the console open. Publish port 9001 on 127.0.0.1 unless strict mode is on.

Test results

Check Result
cargo test --workspace 1227 passed
VERIFY_STAGE=full bash scripts/s3-autoloop/verify.sh 26/26, on this branch and on the base commit
npm run test:e2e use cases 5 use cases, each passing twice; a separate continuous demo run also passed
Mutations of the feature code 14 of 14 made a test fail

The base commit's end-to-end results matched the values predicted before running them, except the two predictions that were corrected and rerun: the unreachable console renders a blank page, and the existing gate stops early when the new tests are overlaid on the base commit because they do not compile there.

Known limits

  • Signed chunks were not exercised by a real SDK. boto3 and the JavaScript SDK sent other payload modes in these tests. For aws-sdk-go-v2 the attempt was inconclusive: the header of its seekable upload was not observed, and its streaming upload failed before sending. Evidence is the suite's own signer over a real socket, plus the worked example from the AWS documentation (transcribed from memory; its signatures verify). The AWS Java SDK was not tried.
  • Trailing checksums such as x-amz-checksum-crc32 are accepted but not validated.
  • DeleteObjects and PostObject still return 405, as before. SigV2 and SigV4a are not implemented.
  • In the console's object table, the actions cell uses display:flex, so its bottom border sits slightly lower than the rest of the row. It is cosmetic and not fixed here.
  • This does not publish anything to Docker Hub: a new tag still has to be built and pushed.

…image

CORS: PutBucketCors/GetBucketCors/DeleteBucketCors, preflight, and response
headers, plus DEVCLOUD_S3_CORS_ALLOW_ORIGINS as a default for buckets without
their own rules. Nothing is allowed unless configured.

Web console: embedded React app and JSON API on DEVCLOUD_S3_CONSOLE_ADDR (9001
in the image). Mutations go through the S3 provider protocol. Strict mode adds
HTTP Basic; a Host allowlist stops DNS rebinding.

SigV4 in strict mode: verify aws-chunked chunk and trailer signatures, enforce
a 15-minute clock skew and credential scope date, require host and behavioural
x-amz-* headers to be signed, and compare presigned signatures in constant
time. The binary refuses an unknown auth mode or strict mode without credentials.

Fixes:
- Keys with spaces, non-ASCII, or reserved characters failed strict auth because
  the canonical URI was double-encoded.
- aws-chunked framing was stored as part of the object in relaxed mode.
- A non-ASCII x-amz-date panicked while holding the store lock.
- Transfer-Encoding: chunked bodies were read as empty; conflicting
  Content-Length values are now refused.
Builds the standalone image and exercises it through real sockets, Chromium,
and the AWS SDK for JavaScript: CORS from a browser, signed requests, the web
console, image startup, and the existing S3 acceptance gate.
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 6, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-06T03:55:29.137199Z 51fdbf4 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 51fdbf484a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread web/s3-console/src/App.tsx Outdated
Comment on lines +148 to +149
const parts = prefix.split('/').filter(Boolean)
return parts.map((part, index) => ({ label: part, prefix: `${parts.slice(0, index + 1).join('/')}/` }))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve empty path segments in breadcrumb destinations

For valid S3 keys containing consecutive or leading slashes, filtering out empty segments changes the prefix that breadcrumb buttons navigate to. For example, browsing a//b/ and clicking its b breadcrumb navigates to a/b/, a different object namespace; subsequent uploads then target that different prefix and can overwrite objects there. Build breadcrumb destinations from slices of the original prefix rather than reconstructing them from nonempty segments.

Useful? React with 👍 / 👎.

@simota
simota merged commit 6ab3e30 into main Nov 6, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant